import copy
from decimal import Decimal
from typing import List

from src.core.backtest.context import Context
from src.core.domain.balance import Balance
from src.core.domain.currency import Currency
from src.core.domain.position import Position
from src.core.instruction.instruction import Instruction
from src.core.strategy.strategy import Strategy
from src.enums.enums import PositionStatus, Direction

ZERO = Decimal('0')
OPEN_FEE_RATIO = Decimal('0.0005')
CLOSE_FEE_RATIO = Decimal('0.0002')
SLIPPAGE_RATIO = Decimal('0.0005')
ISOLATED_LEVER = Decimal('10')  # 计算逐仓的保证金使用的杠杆倍数


def _calculate_margin(instruction: Instruction) -> Decimal:
    return instruction.price * instruction.quantity / ISOLATED_LEVER


def _calculate_fee(instruction: Instruction) -> Decimal:
    """
    计算开仓/平仓手续费
    :param instruction: instruction
    :return: 手续费金额
    """
    quantity = instruction.quantity
    price = instruction.price
    if instruction.is_open():
        return price * quantity * OPEN_FEE_RATIO
    else:
        return price * quantity * CLOSE_FEE_RATIO


def _calculate_slippage_price(instruction: Instruction) -> Decimal:
    price = instruction.price
    direction = instruction.direction
    if direction == Direction.LONG:
        if instruction.is_open():
            return price * (1 + SLIPPAGE_RATIO)
        else:
            return price * (1 - SLIPPAGE_RATIO)
    if direction == Direction.SHORT:
        if instruction.is_open():
            return price * (1 - SLIPPAGE_RATIO)
        else:
            return price * (1 + SLIPPAGE_RATIO)
    return price


class Portfolio:
    """
    投资组合管理器，回测系统的核心执行单元。

    负责将策略生成的抽象信号转化为具体的仓位操作，并管理仓位的整个生命周期，
    包括开仓、更新市值、风险管理退出、策略信号退出等。
    """

    def __init__(self, balance: Balance, currency: Currency):
        """
        Args:
            balance (Balance): 一个Balance实例，用于所有资金相关的计算和更新。
        """
        self.currency = currency
        self.balance = balance
        self.active_positions: List[Position] = []
        self.closed_positions: List[Position] = []

    def update_market_value(self, context: Context):
        """
        在每个时间点，根据最新的市场价格更新所有活跃仓位的市值和浮动盈亏。
        """
        total_unrealized_pnl = ZERO
        if not self.active_positions:
            # 如果没有持仓，确保权益与余额一致
            self.balance.update_market_value(ZERO, context.current_time)
            return

        latest_bar = context.get_latest_bar()
        if latest_bar is None:
            return

        # 假设我们只交易一个资产，其价格在 'close' 列
        # 在多资产系统中，需要根据仓位的symbol来获取对应价格
        current_price = Decimal(str(latest_bar['close']))

        for pos in self.active_positions:
            pos.update_market_price(current_price)
            total_unrealized_pnl += pos.unrealized_pnl

        # 将总的浮动盈亏传递给Balance对象，以更新整个账户的权益
        self.balance.update_market_value(total_unrealized_pnl, context.current_time)

    def check_risk_management_exits(self, strategy: Strategy, context: Context):
        """
        检查所有活跃仓位是否触发了止损或止盈条件。
        """
        positions_to_close = []
        for pos in self.active_positions:
            # 检查止损
            stop_loss_manager = pos.stop_loss_manager
            if not stop_loss_manager:
                continue
            if stop_loss_manager and stop_loss_manager.check_exit(pos, context):
                print(f"INFO ({context.current_time}): STOP-LOSS triggered for position {pos.id}")
                positions_to_close.append(pos)
                # 调用策略的回调钩子
                # strategy.on_stop_loss(pos, context) # 这部分逻辑应在引擎中处理或在此处传递strategy
                continue  # 如果止损，就不再检查止盈
            # 检查止盈
            take_profit_manager = pos.take_profit_manager
            if take_profit_manager and take_profit_manager.check_exit(pos, context):
                print(f"INFO ({context.current_time}): TAKE-PROFIT triggered for position {pos.id}")
                positions_to_close.append(pos)
                # strategy.on_take_profit(pos, context)

        for pos in positions_to_close:
            self._close_position(strategy, pos, context)

    def check_strategy_exits(self, strategy: Strategy, context: Context):
        """
        检查策略本身是否发出了平仓信号。
        """
        positions_to_close = []
        for pos in self.active_positions:
            exit_signal, _ = strategy.generate_exit_signal(context)
            # 如果退出信号与持仓方向相反，则平仓
            if (pos.direction == Direction.LONG and exit_signal == Direction.SHORT) or \
                    (pos.direction == Direction.SHORT and exit_signal == Direction.LONG):
                print(f"INFO ({context.current_time}): Strategy EXIT signal received for position {pos.id}")
                positions_to_close.append(pos)

        for pos in positions_to_close:
            self._close_position(strategy, pos, context)

    def check_strategy_entries(self, strategy: Strategy, context: Context):
        """
        检查策略是否发出了开仓信号，并执行开仓操作。
        简化假设：只允许持有一个仓位。
        """
        max_add_ons = strategy.get_max_add_ons()
        if len(self.active_positions) >= max_add_ons:
            return  # 如果已有持仓数量大于最大持仓数量，则不考虑开新仓

        instruction = strategy.generate_entry_signal(context)
        if instruction is None:
            return
        direction = instruction.direction
        latest_bar = context.get_latest_bar()
        if latest_bar is None:
            return
        price = Decimal(str(latest_bar['close']))
        # 使用策略的Sizer计算仓位大小
        size = strategy.sizer.calculate_size(self.balance, context, price, self.currency)
        if size <= ZERO:
            return
        # 简化保证金计算：仓位价值 / 杠杆
        required_margin = _calculate_margin(instruction)
        open_fee = price * size * OPEN_FEE_RATIO
        if self.balance.available_margin < required_margin:
            print(f"WARNING ({context.current_time}): Not enough available margin to open position.")
            return
        # 执行开仓
        self._open_position(direction, size, price, open_fee, required_margin, context, strategy)

    def _open_position(self,
                       direction: Direction,
                       size: Decimal,
                       price: Decimal,
                       open_fee: Decimal,
                       margin: Decimal,
                       context: Context,
                       strategy: Strategy):
        pos = Position(
            symbol=self.currency.symbol,
            direction=direction,
            quantity=size,
            entry_price=price,
            entry_time=context.current_time,
            open_fee=open_fee,
            margin=margin,
        )
        pos.stop_loss_manager = copy.deepcopy(strategy.stop_loss_manager)
        pos.take_profit_manager = copy.deepcopy(strategy.take_profit_manager)
        self.active_positions.append(pos)
        self.balance.on_position_opened(margin)
        strategy.on_position_opened(pos, context)  # 调用策略回调
        print(f"INFO ({context.current_time}): Opened {direction.name} position {pos.id} | Size: {size} @ {price}")

    def _close_position(self, strategy: Strategy, pos: Position, context: Context):
        if pos.status == PositionStatus.CLOSED:
            return

        latest_bar = context.get_latest_bar()
        price = Decimal(str(latest_bar['close']))
        close_fee = price * pos.quantity * CLOSE_FEE_RATIO
        pos.close(price, context.current_time, close_fee)
        margin_freed = (pos.entry_price * pos.quantity) / self.balance.leverage

        self.balance.on_position_closed(pos.net_pnl, margin_freed)
        self.active_positions.remove(pos)
        self.closed_positions.append(pos)
        strategy.on_position_closed(pos, context)  # 调用策略回调
        print(f"INFO ({context.current_time}): Closed position {pos.id} @ {price} | PnL: {pos.net_pnl:.2f}")

    def get_total_trades(self) -> int:
        """返回已完成的交易总数。"""
        return len(self.closed_positions)
